/*
 * Copyright:  2018 smarabbit studio.
 *
 *  Licensed under the Confluent Community License; you may not use this file
 *  except in compliance with the License.  You may obtain a copy of the License at
 *
 *  http://www.confluent.io/confluent-community-license
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 *  WARRANTIES OF ANY KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations under the License.
 *
 *  @作   者： 黄开晖 (117227773@qq.com)
 *  @日   期:  2021年11月1日
 */
package com.massyframework.beanskin.springboot.beancontext;

import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

import javax.servlet.ServletContext;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.support.ServletContextApplicationContextInitializer;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.util.Assert;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.ConfigurableWebEnvironment;
import org.springframework.web.context.WebApplicationContext;

import com.massyframework.beanskin.assembly.Assembly;
import com.massyframework.beanskin.assembly.ServletModel;
import com.massyframework.beanskin.springboot.beancontext.freemarker.FreeMarkerConfig;

/**
 * Web应用系统初始化器
 * @author huangkh
 *
 */
public class ServletApplicationLauncher extends SpringBootServletInitializer{

	private final Assembly assembly;
	private final ApplicationContextInitializer<ConfigurableWebApplicationContext> initializer;
	 
	/**
	 * 
	 */
	public ServletApplicationLauncher(Assembly assembly, 
			ApplicationContextInitializer<ConfigurableWebApplicationContext> initializer) {
		this.assembly = Objects.requireNonNull(assembly, "\"assmebly\" cannot be null.");
        this.initializer = Objects.requireNonNull(initializer, "\"initializer\" cannot be null.");
    }

	@Override
	protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
		SpringApplicationBuilder builder = createSpringApplicationBuilder();
		builder.main(getClass());
		
		/*
		ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
		if (parent != null) {
			this.logger.info("Root context already created (using as parent).");
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
			builder.initializers(new ParentContextApplicationContextInitializer(parent));
		}
		*/

		Class<?>[] annoTypes = assembly.getConfig().getConfigTypes();
		if (annoTypes.length > 0) {
			builder.sources(annoTypes);
		}

		builder.initializers(new ServletContextApplicationContextInitializer(servletContext));
		builder.contextFactory((webApplicationType) -> new AnnotationConfigServletWebServerApplicationContext());
		builder = configure(builder);
		
		builder.listeners(new WebEnvironmentPropertySourceInitializer(this.assembly, servletContext));
	
		SpringApplication application = builder.build();
		if (application.getAllSources().isEmpty()
				&& MergedAnnotations.from(getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) {
			application.addPrimarySources(Collections.singleton(getClass()));
		}
		Assert.state(!application.getAllSources().isEmpty(),
				"No SpringApplication sources have been defined. Either override the "
						+ "configure method or add an @Configuration annotation");

		application.setRegisterShutdownHook(false);
		return run(application);
	}
	
	

	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
		//配置类
		Class<?>[] annoTypes = this.assembly.getConfig().getConfigTypes();
		if (annoTypes.length == 0) {
			throw new RuntimeException("cannot found spring boot configuration class.");
		}
		
		Set<Class<?>> types = new HashSet<Class<?>>();
		
		String text = this.assembly.getConfig().getParameter(FreeMarkerConfig.ENABLED, "false");
		if (text.equalsIgnoreCase("true")) {
			types.add(FreeMarkerConfig.class);
		}

		
		for (Class<?> annoType: annoTypes) {
			if (annoType.isAnnotationPresent(EnableAutoConfiguration.class)){
				if (this.logger.isWarnEnabled()) {
					this.logger.warn("cannot use EnableAutoConfiguration with beanskin, suggest cancel it: module=" + assembly.toIdentifier());
				}
			}
			types.add(annoType);
		}
		
		//懒加载
		text = assembly.getConfig().getParameter("lazy.init", "true");
		Boolean defaultLazyInit = text == null ? null : Boolean.parseBoolean(text);		
		
		//判断是否servlet
		if (assembly.getConfig().getServletModel() == ServletModel.SERVLET) {
			defaultLazyInit = false;
		}
		
		return builder
				.initializers(this.initializer)
				.resourceLoader(new DefaultResourceLoader(assembly.getModuleClassLoader()))
				.sources(types.toArray(new Class<?>[types.size()]))
				.lazyInitialization(defaultLazyInit);
	}

	/**
	 * 获取已存在的RootWebApplicationContext
	 * @param servletContext {@link ServletContext}
	 * @return {@link ApplicationContext}
	 */
	protected ApplicationContext getExistingRootWebApplicationContext(ServletContext servletContext) {
		Object context = servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
		if (context instanceof ApplicationContext) {
			return (ApplicationContext) context;
		}
		return null;
	}
	
    
    /**
	 * {@link ApplicationListener} to trigger
	 * {@link ConfigurableWebEnvironment#initPropertySources(ServletContext, javax.servlet.ServletConfig)}.
	 */
	private static final class WebEnvironmentPropertySourceInitializer
			implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {

		private Assembly assembly;
		private final ServletContext servletContext;

		private WebEnvironmentPropertySourceInitializer(Assembly assembly, ServletContext servletContext) {
			this.assembly = assembly;
			this.servletContext = servletContext;
		}

		@Override
		public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
			ConfigurableEnvironment environment = event.getEnvironment();
			if (environment instanceof ConfigurableWebEnvironment) {
				((ConfigurableWebEnvironment) environment).initPropertySources(this.servletContext, null);
			}
			
			// 设置装配件属性源
			MutablePropertySources propertySources = environment.getPropertySources();
			propertySources.addLast(new AssemblyPropertySource(this.assembly));
		}

		@Override
		public int getOrder() {
			return Ordered.HIGHEST_PRECEDENCE;
		}

	}
}
